/**
 * 
 */
package darwin8086.genetic;

import darwin8086.war.WarriorRepository;

import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.*;

/**
 * @author Dean
 *
 */
public class Generation {
	
	protected ArrayList<Gene> geneMap;
	protected String[] botList;
	protected int botSize;
	protected int choiceIndex;
	final MutationOp op;
	String path;
	
	public Generation(String dir, int genSz, int botSz, MutationOp newOp) {
		op = newOp;
		path=dir + "/";
		geneMap = new ArrayList<Gene>(genSz);
		for(int i=0;i<genSz;i++) {
			geneMap.add(new Gene(i));
		}
		botList = new String[botSz];
		botSize = botSz;
		choiceIndex = 0;
		for(int i=0;i<botSz;i++) {
			botList[i] = "bot" + i + "f.com";
		}
	}
	
	void WriteFitnessReport(String fileName) {
		FileOutputStream out; 
		try {
			 out = new FileOutputStream(path + fileName);
			 PrintStream ps = new PrintStream(out);
			 ps.print("Id,TimesRun,Fitness\n");
			 for(Gene gen : geneMap) {
				 ps.print(gen.id + "," + gen.timesRun + "," + gen.fitness + "\n"); 
			 }
		     out.close();
		}
        catch (Exception e)
        {
                System.err.println ("Error writing to file");
        }
	}
	
	void WriteAll() {
		for (Gene gen : geneMap) {
			gen.WriteToFile(path);
		}
	}
	
	void WriteToBots() {		
		for(int i=0;i<botSize;i++){ //choose from generation and write to bots
			Gene temp = geneMap.get(choiceIndex+i);
			temp.WriteToFile("survivors/", botList[i]);
			temp.IncrementTimesRun();
		}
	}
	
	public void UpdateAllFitness(WarriorRepository wr) {
		for(int i=0;i<botSize;i++) {
			geneMap.get(choiceIndex+i).UpdateFitness(wr.getScore(botList[i]));
		}
		choiceIndex = (choiceIndex+botSize) % geneMap.size();
	}
	
	public void Mutate(int mutationRate) {
		mutationRate = mutationRate - (mutationRate%2); //if mutationRate is not even, decrease
		final int offset = geneMap.size() - 1 - mutationRate;
		Collections.sort(geneMap); //genes sorted by fitness
		if(op == MutationOp.ONEPOINT) {
			for(int i=mutationRate;i>0;i=i-2) {
				geneMap.get(i).SetChromosome(OnePointCrossover(geneMap.get(i+offset).GetChromosome(),geneMap.get(i-1+offset).GetChromosome()));
				geneMap.get(i-1).SetChromosome(OnePointCrossover(geneMap.get(i-1+offset).GetChromosome(),geneMap.get(i+offset).GetChromosome()));
			}
		}
		else if(op == MutationOp.TWOPOINT) {
			for(int i=mutationRate;i>0;i=i-2) {
				geneMap.get(i).SetChromosome(TwoPointCrossover(geneMap.get(i+offset).GetChromosome(),geneMap.get(i-1+offset).GetChromosome()));
				geneMap.get(i-1).SetChromosome(TwoPointCrossover(geneMap.get(i-1+offset).GetChromosome(),geneMap.get(i+offset).GetChromosome()));
			}
		}
		else if(op == MutationOp.CUTSLPICE) {
			Random randGen = new Random();
			int smallerLength;
			for(int i=mutationRate;i>0;i=i-2) {
				smallerLength = Math.min(geneMap.get(i+offset).GetChromosome().length, geneMap.get(i+offset).GetChromosome().length);
				int split = randGen.nextInt(smallerLength);
				geneMap.get(i).SetChromosome(CutSplice(geneMap.get(i+offset).GetChromosome(),geneMap.get(i-1+offset).GetChromosome(),split));
				geneMap.get(i-1).SetChromosome(CutSplice(geneMap.get(i-1+offset).GetChromosome(),geneMap.get(i+offset).GetChromosome(),split));
			}
		}
		else if(op == MutationOp.UNIFORM) {
			for(int i=mutationRate;i>0;i=i-2) {
				geneMap.get(i).SetChromosome(UniformCrossover(geneMap.get(i+offset).GetChromosome(),geneMap.get(i-1+offset).GetChromosome()));
				geneMap.get(i-1).SetChromosome(UniformCrossover(geneMap.get(i-1+offset).GetChromosome(),geneMap.get(i+offset).GetChromosome()));
			}
		}
		
		else { //default is RMUTATION
			/***** NEEDS CHECK *****/
			final int segments = 4; //how many segments for the unfit population
			final int segmentSz = mutationRate/segments;
			for(int i=0;i<segments;i++) { 
				for(int j=0;j<segmentSz;j++) {
					/* segments the mutationRte population and changes amount of bytes depending on segment */
					/* for example if segments=4, then 1st quarter will be 100% mutated, 2nd quarter 75% and*/
					/* 3rd quarter be 50% replaced, 4th quarter 25% replaced                                */
					int bytesMutate = (int)((float)(geneMap.get((i*segmentSz)+j).GetChromosome().length)*((float)(segments-i)/segments)); 
					geneMap.get((i*segmentSz)+j).SetChromosome(RelativeMutation(geneMap.get((i*segmentSz)+j).GetChromosome(),bytesMutate));
				}
			}
		}
		/* we choose the bots one by one from the array in order, so to make the fighting random, we shuffle */
		/* the array after mutation */
		Collections.shuffle(geneMap);
	}
	
	private byte[] UniformCrossover(byte[] b1, byte[] b2) {
		/* do uniform cross over - zero out even bits in i, zero out odd bits in i-1 and then OR the two */
		if(b1.length != b2.length) {
			/****throw something****/
		}
		byte[] mutated = new byte[b1.length]; 
		Random gen = new Random();
		byte randomChoiceBits = (byte)gen.nextInt();
		for(int i=0;i<b1.length;i++) {
			byte choice = (byte) (b1[i] & randomChoiceBits);
			byte negChoice = (byte)(b2[i] & (~randomChoiceBits));
			mutated[i] = (byte)(choice | negChoice);
		}
		return mutated;
	}
	
	private byte[] OnePointCrossover(byte[] b1, byte[] b2) {
		int length = Math.min(b1.length,b2.length);
		byte[] mutated = new byte[length]; 
		for(int i=0;i<length;i++) {
			byte firstHalf = (byte) (b1[i] & 0xF0); //zero out second half
			byte secondHalf = (byte)(b2[i] & 0x0F);//zero out first half
			mutated[i] = (byte)(secondHalf | firstHalf);
		}
		return mutated;
	}
	
	private byte[] TwoPointCrossover(byte[] b1, byte[] b2) {
		int length = Math.min(b1.length,b2.length);
		byte[] mutated = new byte[length]; 
		for(int i=0;i<length;i++) {
			byte firstThirdQuarter = (byte) (b1[i] & 0xCC); //zero out second and fourth quarters
			byte secondFourthQuarter = (byte)(b2[i] & 0x33);//zero out out first and third quarters 
			mutated[i] = (byte)(firstThirdQuarter | secondFourthQuarter);
		}
		return mutated;
	}
	
	private byte[] CutSplice(byte[] b1, byte[] b2, int pos) {
		int length = Math.min(b1.length,b2.length);
		/*****if(length>=pos) throw*****/
		byte[] mutated = new byte[length]; 
		for(int i=0;i<pos;i++) {
			mutated[i]=b1[i];
		}
		for(int i=pos;i<length;i++) {
			mutated[i]=b2[i];
		}
		return mutated;
	}
	
	private byte[] RelativeMutation(byte[] b, int numBytes) {
		/***** NEEDS CHECK *****/
		byte[] mutated = new byte[b.length];
		System.arraycopy(b, 0, mutated, 0, b.length);
		Random randGen = new Random();
		for(int i=0;i<numBytes;i++) {
			mutated[randGen.nextInt(b.length)] = (byte)randGen.nextInt();
		}
		return mutated;
	}
}
